#include "ARI.h"
#include "typeinfo"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

static bool transformou;

Canonizacao::Canonizacao() {}

bool Canonizacao::houveTransformacao() {
    bool aux = transformou;
    transformou = false;
    return aux;
}


bool comutaCanonizacao(Stm* s, Exp* e) {
    NAME *nome = dynamic_cast<NAME*>(e);
    CONST *consti = dynamic_cast<CONST*>(e);
    CONSTF *constf = dynamic_cast<CONSTF*>(e);
    CONST *stmExpConsti = NULL;
    CONSTF *stmExpConstf = NULL;
    EXP *exp = dynamic_cast<EXP*>(s);
    if(exp != NULL) {
        stmExpConsti = dynamic_cast<CONST*>(exp->expressao);
        stmExpConstf = dynamic_cast<CONSTF*>(exp->expressao);
    }
    if(nome || consti || constf || (exp && (stmExpConsti ||stmExpConstf)))
        return true;
    return false;
}

///Remove os Stmlist encadeados em um SmtList
StmList* removeStmList(StmList* avalia){
    StmList* novoStmList, *stmListTemp;

    stmListTemp = avalia;
    if (stmListTemp != NULL){

        while (dynamic_cast<ExpList*>(stmListTemp->prim))
            stmListTemp = dynamic_cast<StmList*>(stmListTemp->prim);

        novoStmList = stmListTemp;
        novoStmList->prox = removeStmList(stmListTemp->prox);
        return novoStmList;
    }
    return NULL;
}
///Remove StmList encadeados em um stm
Stm* removeStmList(Stm* avalia){
    Stm* novoStm;
    StmList*stmListTemp;
    stmListTemp = dynamic_cast<StmList*>(avalia);
    if (stmListTemp != NULL){

        while (dynamic_cast<StmList*>(stmListTemp->prim))
            stmListTemp = dynamic_cast<StmList*>(stmListTemp->prim);

        novoStm = stmListTemp->prim;
        return removeStmList(novoStm);
    }
    return avalia;
}
///Remove os Explist encadeados em um explist
ExpList* removeExpList(ExpList* avalia){
    ExpList* novoExpList, *expListTemp;

    expListTemp = avalia;
    if (expListTemp != NULL){

        while (dynamic_cast<ExpList*>(expListTemp->prim))
            expListTemp = dynamic_cast<ExpList*>(expListTemp->prim);

        novoExpList = expListTemp;
        novoExpList->prox = removeExpList(expListTemp->prox);
        return novoExpList;
    }
    return NULL;
}

///Remove os Explist encadeados em um Exp
Exp* removeExpList(Exp* avalia){
    Exp* novoExp;
    ExpList*expListTemp;
    expListTemp = dynamic_cast<ExpList*>(avalia);
    if (expListTemp != NULL){

        while (dynamic_cast<ExpList*>(expListTemp->prim))
            expListTemp = dynamic_cast<ExpList*>(expListTemp->prim);

        novoExp = expListTemp->prim;
        return removeExpList(novoExp);
    }
    return avalia;
}

Exp* removeEseq(Exp* expr) {
    ///ESEQ(s1,ESEQ(s2,e)) = ESEQ(SEQ(s1,s2),e)
    ESEQ* eseq_raiz = dynamic_cast<ESEQ*>(expr);
    if(eseq_raiz != NULL) {
        ESEQ* eseq_expressao = dynamic_cast<ESEQ*>(eseq_raiz->expressao);
        if(eseq_expressao != NULL) {
            ESEQ* retornoEseq = new ESEQ(
                new SEQ(
                    eseq_raiz->sentenca,
                    eseq_expressao->sentenca),
                eseq_expressao->expressao);

            transformou = true;
            return retornoEseq;
        }
    }

    ///TRATANDO BINOP
    BINOP* binop_raiz = dynamic_cast<BINOP*>(expr);
    if(binop_raiz != NULL) {
        ///BINOP(op,ESEQ(s,e1),e2) = ESEQ(s,BINOP(op,e1,e2))
        ESEQ* binop_expressao1 = dynamic_cast<ESEQ*>(binop_raiz->expressao1);
        if(binop_expressao1 != NULL) {
            ESEQ* retornoBinop = new ESEQ(
                binop_expressao1->sentenca,
                new BINOP(
                    binop_raiz->operador,
                    binop_expressao1->expressao,
                    binop_raiz->expressao2));

            transformou = true;
            return retornoBinop;
        }

        ///BINOP(op,const(n),ESEQ(s,e)) = ESEQ(s,BINOP(op,const(n),e))
        ESEQ* binop_espressao2 = dynamic_cast<ESEQ*>(binop_raiz->expressao2);
        if(binop_espressao2 != NULL) {
            if(comutaCanonizacao(binop_espressao2->sentenca, binop_raiz->expressao1)) {
                ESEQ* retornoEseq = new ESEQ(
                    binop_espressao2->sentenca,
                    new BINOP(
                        binop_raiz->operador,
                        binop_raiz->expressao1,
                        binop_espressao2->expressao));

                transformou = true;
                return retornoEseq;
            } else {
                ///BINOP(op,e1,ESEQ(s,e2)) = ESEQ(MOVE(TEMP(t),e1),ESEQ(s,BINOP(op,TEMP(t),e2)))
                Temp* t = new Temp();
                ESEQ* retornoEseq = new ESEQ(
                    new MOVE(
                        new TEMP(t),
                        binop_raiz->expressao1),
                    new ESEQ(
                        binop_espressao2->sentenca,
                        new BINOP(
                            binop_raiz->operador,
                            new TEMP(t),
                            binop_espressao2->expressao)));

                transformou = true;
                return retornoEseq;
            }
        }
    }

    ///MEM(ESEQ(s,e)) = ESEQ(s,MEM(e))
    MEM* mem_raiz = dynamic_cast<MEM*>(expr);
    if(mem_raiz != NULL) {
        ESEQ* mem_expressao = dynamic_cast<ESEQ*>(mem_raiz->expressao);
        if(mem_expressao != NULL) {
            ESEQ* retornoEseq = new ESEQ(
                mem_expressao->sentenca,
                new MEM(
                    mem_expressao->expressao));

            transformou = true;
            return retornoEseq;
        }
    }

    ///TRATAMENTO DO CALL
    CALL* chamada_raiz = dynamic_cast<CALL*>(expr);
    ///CALL(f,args) = ESEQ(MOVE(TEMP(tnew),CALL(f,args)),TEMP(tnew))
    if(chamada_raiz != NULL && !chamada_raiz->modificou) {
        ExpList* chamada_argumentos = dynamic_cast<ExpList*>(chamada_raiz->argumentos);
        if(chamada_argumentos != NULL){
            ESEQ* chamada_eseq = dynamic_cast<ESEQ*>(chamada_argumentos->prim);
            if(chamada_eseq != NULL){
                Temp* t = new Temp();
                ESEQ* eseqRetorno = new ESEQ(
                                                 new MOVE(
                                                          new TEMP(t),
                                                          chamada_eseq
                                                          ),
                                                 new CALL(
                                                          chamada_raiz->funcao,
                                                          new ExpList(
                                                                      new TEMP(t),
                                                                      chamada_argumentos->prox
                                                                      )
                                                          )


                                                 );
                            return eseqRetorno;
            }
        }
        ///CALL(f,args) = ESEQ(MOVE(TEMP(t),CALL(f,args)),TEMP(t))
        else{
            Temp* t = new Temp();
            ESEQ* retornoCall = new ESEQ(
                new MOVE(new TEMP(t), chamada_raiz), new TEMP(t));

                chamada_raiz->modificou = true;

            transformou = true;
            return retornoCall;
        }
    }

    return expr;
}


Stm* removeEseq(Stm* stm_teste) {

    ///JUMP(ESEQ(s,e) = SEQ(s,JUMP(e))
    JUMP* jump_raiz = dynamic_cast<JUMP*>(stm_teste);
    if(jump_raiz != NULL) {
        ESEQ* jump_rotulo = dynamic_cast<ESEQ*>(jump_raiz->l);
        if(jump_rotulo != NULL) {
            SEQ* retornoSeq = new SEQ(
                jump_rotulo->sentenca,
                new JUMP(
                    jump_raiz->l));

            transformou = true;
            return retornoSeq;
        }
    }

    ///CJUMP(op,ESEQ(s,e1),e2,l1,l2) = SEQ(s,CJUMP(op,e1,e2,l1,l2))
    CJUMP* cjump_raiz = dynamic_cast<CJUMP*>(stm_teste);
    if(cjump_raiz != NULL) {
        ESEQ* cjump_esq = dynamic_cast<ESEQ*>(cjump_raiz->esq);
        if(cjump_esq != NULL) {
            SEQ* retornoSeq = new SEQ(
                cjump_esq->sentenca,
                new CJUMP(
                    cjump_raiz->relop,
                    cjump_esq->expressao,
                    cjump_raiz->dir,
                    cjump_raiz->casoVerdadeiro,
                    cjump_raiz->casoFalso));

            transformou = true;
            return retornoSeq;
        }
        ///CJUMP(op,Name(l),ESEQ(s,e),l1,l2) = SEQ(s,CJUMP(op,Name(l),e,l1,l2)
        ESEQ* cjump_dir = dynamic_cast<ESEQ*>(cjump_raiz->dir);
        if(cjump_dir != NULL) {
            if(comutaCanonizacao(cjump_dir->sentenca, cjump_raiz->esq)) {
                SEQ* retornoSeq = new SEQ(
                    cjump_dir->sentenca,
                    new CJUMP(
                        cjump_raiz->relop,
                        cjump_raiz->esq,
                        cjump_dir->expressao,
                        cjump_raiz->casoVerdadeiro,
                        cjump_raiz->casoFalso));

                transformou = true;
                return retornoSeq;
            } else {
            ///CJUMP(op,e1,ESEQ(s,e2),l1,l2) = SEQ(MOVE(TEMP(t),e1),SEQ(s,CJUMP(op,TEMP(t),e2,l1,l2)
                Temp* t = new Temp();
                SEQ* retornoSeq = new SEQ(
                    new MOVE(
                        new TEMP(t),
                        cjump_raiz->esq),
                    new SEQ(
                        cjump_dir->sentenca,
                        new CJUMP(
                            cjump_raiz->relop,
                            new TEMP(t),
                            cjump_dir->expressao,
                            cjump_raiz->casoVerdadeiro,
                            cjump_raiz->casoFalso)));

                transformou = true;
                return retornoSeq;
            }
        }
    }

    MOVE* move_raiz = dynamic_cast<MOVE*>(stm_teste);
    if(move_raiz != NULL) {
        ///MOVE(ESEQ(s,e1),e2) = SEQ(s,MOVE(e1,e2))
        ESEQ* move_destino = dynamic_cast<ESEQ*>(move_raiz->destino);
        if(move_destino != NULL) {
            SEQ* retornoSeq= new SEQ(
                move_destino->sentenca,
                new MOVE(
                    move_destino->expressao,
                    move_raiz->origem));

            transformou = true;
            return retornoSeq;
        }
        ///MOVE(e1,ESEQ(s,e2)) = SEQ(MOVE(TEMP(t),e1),SEQ(s,MOVE(TEMP(t),e2)))
        ESEQ* move_origem = dynamic_cast<ESEQ*>(move_raiz->origem);
        if(move_origem != NULL) {
            Temp* t = new Temp();
            SEQ* retornoSeq = new SEQ(
                new MOVE(
                    new TEMP(t),
                    move_raiz->destino),
                new SEQ(
                    move_origem->sentenca,
                    new MOVE(
                        new TEMP(t),
                        move_origem->expressao)));

            transformou = true;
            return retornoSeq;
        }
    }

    EXP* exp_raiz = dynamic_cast<EXP*>(stm_teste);
    if(exp_raiz != NULL) {
        ESEQ* eseq_teste = dynamic_cast<ESEQ*>(exp_raiz->expressao);
        if(eseq_teste != NULL) {
            SEQ* retornoSeq = new SEQ(
                eseq_teste->sentenca,
                new EXP(eseq_teste->expressao));

            transformou = true;
            return retornoSeq;
        }
    }
    return stm_teste;
}

///*-----FrameMIPS-----*///
void Canonizacao::visitar(FrameMIPS *v) {
    if(v != NULL) {
        //fprintf(stdout, "FrameMIPS");
        if(v->rotulo != NULL) v->rotulo->aceitar(this);
        if(v->dadosLocais != NULL) v->dadosLocais->aceitar(this);

    }
}

///*-----FragmentList-----*///
void Canonizacao::visitar(FragmentList *v) {
    if(v != NULL) {
        //fprintf(stdout"FragmentList\n");
        if(v->fragmento != NULL) v->fragmento->aceitar(this);
        if(v->fragmentList != NULL) v->fragmentList->aceitar(this);
    }
}

///*-----Procedure-----*///
void Canonizacao::visitar(Procedure *v) {
    if(v != NULL) {
        //fprintf(stdout"Procedure\n");
        if(v->frame != NULL) v->frame->aceitar(this);

        if(v->corpo != NULL) {
            v->corpo = (StmList*) removeEseq(v->corpo);
            v->corpo->aceitar(this);
        }
    }
}

///*-----Literal-----*///
void Canonizacao::visitar(Literal *v) {
    if(v != NULL) {
        //fprintf(stdout"Literal\n");
        if(v->l != NULL) v->l->aceitar(this);

    }
}

///*-----Inteiro-----*///
void Canonizacao::visitar(Inteiro *v) {
    if(v != NULL) {
        //fprintf(stdout"_Inteiro\n");
        if(v->l != NULL) v->l->aceitar(this);

    }
}

///*-----Real-----*///
void Canonizacao::visitar(Real *v) {
    if(v != NULL) {
        //fprintf(stdout"_Real\n");
        if(v->l != NULL) v->l->aceitar(this);

    }
}

///*-----Logico-----*///
void Canonizacao::visitar(Logico *v) {
    if(v != NULL) {
        //fprintf(stdout"_Logico\n");
        if(v->l != NULL) v->l->aceitar(this);

    }
}

///*-----Registro-----*///
void Canonizacao::visitar(Registro *v) {
    if(v != NULL) {
        //fprintf(stdout"_Registro\n");
        if(v->l) v->l->aceitar(this);

    }
}

///*-----ListaAcesso-----*///
void Canonizacao::visitar(ListaAcesso *v) {
    if(v != NULL) {
        //fprintf(stdout"ListaAcesso\n");
        if(v->local != NULL) v->local->aceitar(this);
        if(v->proximo != NULL) v->proximo->aceitar(this);
    }
}

///*-----inFrame-----*///
void Canonizacao::visitar(inFrame *v) {
    if(v != NULL) {
        //fprintf(stdout"inFrame");

    }
}

///*-----Temp-----*///
void Canonizacao::visitar(Temp *v) {
    if(v != NULL) {
        //fprintf(stdout"Temp\n");

    }
}

///*-----Label-----*///
void Canonizacao::visitar(Label *v) {
    if(v != NULL) {
        //fprintf(stdout"Label\n");

    }
}

///*-----LabelList-----*///
void Canonizacao::visitar(LabelList *v) {
    if(v != NULL) {
        //fprintf(stdout"LabelList\n");
        if(v->label != NULL) v->label->aceitar(this);
        if(v->proximo != NULL) v->proximo->aceitar(this);
    }
}

///*-----StmList-----*///
void Canonizacao::visitar(StmList *v) {
    if(v != NULL) {
        //fprintf(stdout"StmList\n");
        if(v->prim != NULL) {
            v->prim = removeEseq(v->prim);
            v->prim->aceitar(this);
        }
        if(v->prox != NULL) {
            v->prox =(StmList*) removeEseq(v->prox);
            v->prox->aceitar(this);
        }
    }
}

///*-----ExpList-----*///
void Canonizacao::visitar(ExpList *v) {
    if(v != NULL) {
        //fprintf(stdout"ExpList\n");
        v = removeExpList(v);
        if(v->prim != NULL) {
            v->prim = removeEseq(v->prim);
            v->prim->aceitar(this);
        }
        if(v->prox != NULL) {
            v->prox = (ExpList*) removeEseq(v->prox);
            v->prox->aceitar(this);
        }
    }
}

///*-----CONST-----*///
void Canonizacao::visitar(CONST *v) {
    if(v != NULL) {
        //printf("CONST\n");
    }
}

///*-----CONSTF-----*///
void Canonizacao::visitar(CONSTF *v) {
    if(v != NULL) {
        //fprintf(stdout"CONSTF\n");
    }
}

///*-----NAME-----*///
void Canonizacao::visitar(NAME *v) {
    if(v != NULL) {
        //fprintf(stdout"NAME\n");
        if(v->rotulo != NULL) v->rotulo->aceitar(this);
    }
}

///*-----TEMP-----*///
void Canonizacao::visitar(TEMP *v) {
    if(v != NULL) {
        //fprintf(stdout"TEMP\n");
        if(v->temporario != NULL) v->temporario->aceitar(this);
    }
}
///*-----BINOP-----*///
void Canonizacao::visitar(BINOP *v) {
    if(v != NULL) {
        //fprintf(stdout"BINOP");
        if(v->expressao1 != NULL) {
            v->expressao1 = removeExpList(v->expressao1);
            v->expressao1 = removeEseq(v->expressao1);
            v->expressao1->aceitar(this);
        }
        if(v->expressao2 != NULL) {
            v->expressao2 = removeExpList(v->expressao2);
            v->expressao2 = removeEseq(v->expressao2);
            v->expressao2->aceitar(this);
        }
    }
}

///*-----MEM-----*///
void Canonizacao::visitar(MEM *v) {
    if(v != NULL) {
        //fprintf(stdout"MEM\n");
        if(v->expressao != NULL) {
            v->expressao = removeExpList(v->expressao);
            v->expressao = removeEseq(v->expressao);
            v->expressao->aceitar(this);
        }
    }
}

///*-----CALL-----*///
void Canonizacao::visitar(CALL *v) {
    if(v != NULL) {
        //fprintf(stdout"CALL\n");
        if(v->funcao != NULL) {
            v->funcao = removeEseq(v->funcao);
            v->funcao->aceitar(this);
        }
        if(v->argumentos != NULL) {
            v->argumentos = removeExpList(v->argumentos);
            v->argumentos = (ExpList*) removeEseq(v->argumentos);
            v->argumentos->aceitar(this);
        }
    }
}

///*-----ESEQ-----*///
void Canonizacao::visitar(ESEQ *v) {
    if(v != NULL) {
        //fprintf(stdout"ESEQ\n");
        if(v->sentenca != NULL) {
            v->sentenca = removeEseq(v->sentenca);
            v->sentenca->aceitar(this);
        }
        if(v->expressao != NULL) {
            v->expressao = removeEseq(v->expressao);
            v->expressao->aceitar(this);
        }
    }
}

///*-----MOVE-----*///
void Canonizacao::visitar(MOVE *v) {
    if(v != NULL) {
        //fprintf(stdout"MOVE\n");
        if(v->destino != NULL) {
            v->destino = removeEseq(v->destino);
            v->destino->aceitar(this);
        }
        if(v->origem != NULL) {
            v->origem = removeEseq(v->origem);
            v->origem->aceitar(this);
        }
    }
}

///*-----EXP-----*///
void Canonizacao::visitar(EXP *v) {
    if(v != NULL) {
        //fprintf(stdout"EXP");
        if(v->expressao != NULL) {
            v->expressao = removeExpList(v->expressao);
            v->expressao = removeEseq(v->expressao);
            v->expressao->aceitar(this);
        }
    }
}

///*-----JUMP-----*///
void Canonizacao::visitar(JUMP *v) {
    if(v != NULL) {
        //fprintf(stdout"JUMP\n");
        if(v->l != NULL) v->l->aceitar(this);
    }
}

///*-----CJUMP-----*///
void Canonizacao::visitar(CJUMP *v) {
    if(v != NULL) {
        //fprintf(stdout"CJUMP\n");
        if(v->esq != NULL) {
            v->esq = removeEseq(v->esq);
            v->esq->aceitar(this);
        }
        if(v->dir != NULL) {
            v->dir = removeEseq(v->dir);
            v->dir->aceitar(this);
        }
        if(v->casoVerdadeiro != NULL) v->casoVerdadeiro->aceitar(this);
        if(v->casoFalso != NULL) v->casoFalso->aceitar(this);
    }
}

///*-----SEQ-----*///
void Canonizacao::visitar(SEQ *v) {
    if(v != NULL) {
        //fprintf(stdout"SEQ\n");
        if(v->esq != NULL) {
            v->esq = removeEseq(v->esq);
            v->esq->aceitar(this);
        }
        if(v->dir != NULL) {
            v->dir = removeEseq(v->dir);
            v->dir->aceitar(this);
        }
    }
}

///*-----LABEL-----*///
void Canonizacao::visitar(LABEL *v) {
    if(v != NULL) {
        //fprintf(stdout"LABEL\n");
        if(v->l != NULL) v->l->aceitar(this);
    }
}
